package com.gxar.marketdelivery.application.facade;

import com.gxar.marketdelivery.domain.enums.DeliveryStatus;
import com.gxar.marketdelivery.domain.factory.DeliveryFactory;
import com.gxar.marketdelivery.domain.repo.DeliveryPlanRepository;
import com.gxar.marketdelivery.domain.repo.DeliveryPlanSubscriberRepository;
import com.gxar.marketdelivery.domain.repo.DeliveryResSlotRepository;
import com.gxar.marketdelivery.domain.validator.DeliveryPlanValidator;
import com.gxar.marketdelivery.infra.dao.entity.DeliveryPlanEntity;
import com.gxar.marketdelivery.infra.dao.entity.DeliveryPlanSubscriberEntity;
import com.gxar.marketdelivery.infra.dao.entity.DeliveryResSlotEntity;
import com.gxar.marketdelivery.interfaces.web.param.BaseIdParam;
import com.gxar.marketdelivery.interfaces.web.param.CreateDeliveryPlanParam;
import com.gxar.marketdelivery.interfaces.web.param.QueryDeliveryPlanParam;
import com.gxar.marketdelivery.interfaces.web.param.UpdateDeliveryPlanParam;
import com.gxar.marketdelivery.interfaces.web.result.CommonPageResult;
import com.gxar.marketdelivery.interfaces.web.result.DeliveryPlanResult;
import com.gxar.marketdelivery.interfaces.web.result.IResponseStatusMsg;
import com.gxar.marketdelivery.shared.meta.exception.APIRuntimeException;
import com.gxar.marketdelivery.shared.model.DeliveryPlanModel;
import com.gxar.marketdelivery.shared.model.OpInfo;
import com.gxar.marketdelivery.shared.utils.AssemblerUtils2;
import com.gxar.marketdelivery.shared.utils.AssertUtils;
import com.gxar.marketdelivery.shared.utils.CollectionUtils;
import com.gxar.marketdelivery.shared.utils.ExtractUtils;
import com.gxar.marketdelivery.shared.utils.StringSpliceUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DeliveryPlanFacade {

    @Autowired
    protected DeliveryPlanRepository deliveryPlanRepository;
    @Autowired
    protected DeliveryFactory deliveryFactory;
    @Autowired
    protected DeliveryResSlotRepository deliveryResSlotRepository;
    @Autowired
    protected DeliveryPlanValidator planValidator;
    @Autowired
    protected DeliveryPlanSubscriberRepository deliveryPlanSubscriberRepository;

    private final ExecutorService poolExecutor = Executors.newFixedThreadPool(16);

    @Transactional(rollbackFor = Exception.class)
    public void create(CreateDeliveryPlanParam param) {
        validateResSlot(param.getResSlotId());
        planValidator.validatePlan(param);
        deliveryFactory.createDeliveryPlan(param);
    }

    private void validateResSlot(Long resSlotId) {
        DeliveryResSlotEntity resSlotEntity = deliveryResSlotRepository.getById(resSlotId);
        AssertUtils.notNull(resSlotEntity, IResponseStatusMsg.APIEnum.PARAM_ERROR, "资源位不存在");
        Integer limitCount = ObjectUtils.defaultIfNull(resSlotEntity.getResLimitCount(), 0);
        AssertUtils.isTrue(limitCount > 0, IResponseStatusMsg.APIEnum.PARAM_ERROR,
                StringSpliceUtils.splice("资源位:{}-{}不支持投放", resSlotEntity.getId(),
                        resSlotEntity.getResSlotName()));
    }

    @Transactional(rollbackFor = Exception.class)
    public void update(UpdateDeliveryPlanParam param) {
        validateResSlot(param.getResSlotId());
        planValidator.validatePlan(param);
        DeliveryPlanEntity entity = getAndCheck(param.getId());
        boolean canUpdate =
                !DeliveryStatus.OVERDUE.equalByCode(entity.getPlanStatus()) && !DeliveryStatus.FINISH.equalByCode(
                        entity.getPlanStatus());
        AssertUtils.isTrue(canUpdate, IResponseStatusMsg.APIEnum.PARAM_ERROR, "已过期或彻底结束的计划不允许更新");
        deliveryFactory.updateDeliveryPlan(param, entity);
    }

    @Transactional(rollbackFor = Exception.class)
    public void online(BaseIdParam param) {
        OpInfo opInfo = OpInfo.SYS_OP;
        DeliveryPlanEntity entity = getAndCheck(param.getId());

        boolean canOnline =
                DeliveryStatus.WAIT.equalByCode(entity.getPlanStatus()) || DeliveryStatus.OFFLINE.equalByCode(
                        entity.getPlanStatus());
        AssertUtils.isTrue(canOnline, IResponseStatusMsg.APIEnum.PARAM_ERROR,
                "只有待上线和已下线的投放计划才能操作上线");
        online(entity, opInfo);
    }

    @Transactional(rollbackFor = Exception.class)
    public void online(DeliveryPlanEntity entity, OpInfo opInfo) {
        deliveryPlanRepository.updatePlanStatus(entity.getId(), DeliveryStatus.ONLINE.getCode(), opInfo.getOpId());
    }

    @Transactional(rollbackFor = Exception.class)
    public void offline(BaseIdParam param) {
        DeliveryPlanEntity entity = getAndCheck(param.getId());
        OpInfo opInfo = OpInfo.SYS_OP;
        boolean canOffline = DeliveryStatus.ONLINE.equalByCode(entity.getPlanStatus());
        AssertUtils.isTrue(canOffline, IResponseStatusMsg.APIEnum.PARAM_ERROR, "只有已上线的投放计划才能操作下线");
        deliveryPlanRepository.updatePlanStatus(param.getId(), DeliveryStatus.OFFLINE.getCode(), opInfo.getOpId());
    }

    @Transactional(rollbackFor = Exception.class)
    public void finish(BaseIdParam param) {
        DeliveryPlanEntity entity = getAndCheck(param.getId());
        OpInfo opInfo = OpInfo.SYS_OP;

        boolean canFinish =
                DeliveryStatus.OFFLINE.equalByCode(entity.getPlanStatus()) || DeliveryStatus.OVERDUE.equalByCode(
                        entity.getPlanStatus());
        AssertUtils.isTrue(canFinish, IResponseStatusMsg.APIEnum.PARAM_ERROR,
                "只有已下线或已过期的投放计划才能操作彻底结束");
        deliveryPlanRepository.updatePlanStatus(param.getId(), DeliveryStatus.FINISH.getCode(), opInfo.getOpId());
    }

    public void subscribe(BaseIdParam param) {
        getAndCheck(param.getId());
        OpInfo opInfo = OpInfo.SYS_OP;
        DeliveryPlanSubscriberEntity oldSubscriberEntity = deliveryPlanSubscriberRepository.getSubscribe(param.getId(),
                opInfo.getOpId());
        AssertUtils.isNull(oldSubscriberEntity, IResponseStatusMsg.APIEnum.PARAM_ERROR, "您已订阅该计划，请勿重复订阅");
        DeliveryPlanSubscriberEntity subscriberEntity = new DeliveryPlanSubscriberEntity();
        subscriberEntity.setDeliveryPlanId(param.getId());
        subscriberEntity.setSubscriberMis(opInfo.getOpId());
        deliveryPlanSubscriberRepository.create(subscriberEntity);
    }

    public void unsubscribe(BaseIdParam param) {
        DeliveryPlanEntity entity = getAndCheck(param.getId());
        OpInfo opInfo = OpInfo.SYS_OP;
        DeliveryPlanSubscriberEntity oldSubscriberEntity = deliveryPlanSubscriberRepository.getSubscribe(param.getId(),
                opInfo.getOpId());
        AssertUtils.notNull(oldSubscriberEntity, IResponseStatusMsg.APIEnum.PARAM_ERROR, "您未订阅该计划，无法取消订阅");
        deliveryPlanSubscriberRepository.delete(oldSubscriberEntity.getId());
    }

    public CommonPageResult<DeliveryPlanResult> getPageList(QueryDeliveryPlanParam param) {
        CommonPageResult<DeliveryPlanResult> pageResult = new CommonPageResult<>();
        pageResult.setPageSize(param.getPageSize());
        pageResult.setPageNow(param.getPageNow());
        Integer totalCount = deliveryPlanRepository.queryTotalCount(param);
        pageResult.setCount(totalCount.longValue());
        if (totalCount < 1) {
            pageResult.setList(Collections.emptyList());
        } else {
            List<DeliveryPlanEntity> entities = deliveryPlanRepository.queryPageList(param);
            if (CollectionUtils.isEmpty(entities)) {
                pageResult.setList(Collections.emptyList());
            } else {
                List<Long> planIds = ExtractUtils.extractField(entities, DeliveryPlanEntity::getId);
                OpInfo opInfo = OpInfo.SYS_OP;
                List<DeliveryPlanSubscriberEntity> subscriberEntities = deliveryPlanSubscriberRepository.getByDeliveryPlanIds(
                        planIds, opInfo.getOpId());
                List<DeliveryResSlotEntity> byIds = deliveryResSlotRepository.getByIds(entities.stream()
                        .map(DeliveryPlanEntity::getResSlotId).collect(Collectors.toList()));
                pageResult.setList(fillPlan(entities, subscriberEntities, byIds));
            }
        }
        return pageResult;
    }

    private List<DeliveryPlanResult> fillPlan(List<DeliveryPlanEntity> entities,
                                              List<DeliveryPlanSubscriberEntity> subscriberEntities,
                                              List<DeliveryResSlotEntity> resSlotEntities) {
        if (CollectionUtils.isEmpty(entities)) {
            return Collections.emptyList();
        }
        Map<Long, DeliveryPlanEntity> entityMap = CollectionUtils.toMap(entities, DeliveryPlanEntity::getId);
        Map<Long, List<DeliveryPlanSubscriberEntity>> subscribeMap = CollectionUtils.toListMap(subscriberEntities,
                DeliveryPlanSubscriberEntity::getDeliveryPlanId);
        CompletableFuture<List<DeliveryPlanModel>> future = CompletableFuture.supplyAsync(() -> {
            return deliveryFactory.convertToModelAndFill(entities);
        }, poolExecutor);
        try {
            List<DeliveryPlanModel> planModels = future.get();

            Map<Long, DeliveryResSlotEntity> resSlotMap = resSlotEntities.stream().collect(Collectors.toMap(
                    DeliveryResSlotEntity::getId, v -> v));
            return planModels.stream().map((planModel) -> {
                DeliveryPlanResult planResult = AssemblerUtils2.assemble(planModel, DeliveryPlanResult.class);
                planResult.setSubscribe(subscribeMap.containsKey(planModel.getId()));
                DeliveryPlanEntity planEntity = entityMap.get(planModel.getId());
                planResult.setId(String.valueOf(planEntity.getId()));
                Long resSlotId = planEntity.getResSlotId();
                planResult.setResSlotId(String.valueOf(resSlotId));
                planResult.setResSlotName(resSlotMap.get(resSlotId).getResSlotName());
                planResult.setResSlotKey(resSlotMap.get(resSlotId).getResSlotKey());
                planResult.setCreator(planEntity.getCreator());
                planResult.setModifier(planEntity.getModifier());
                planResult.setGmtCreate(planEntity.getGmtCreate());
                planResult.setGmtModify(planEntity.getGmtModify());
                return planResult;
            }).collect(Collectors.toList());
        } catch (InterruptedException | ExecutionException e) {
            throw new APIRuntimeException("获取投放计划失败", e);
        }
    }

    public DeliveryPlanResult getDetail(BaseIdParam param) {
        DeliveryPlanModel planModel = deliveryFactory.getDeliveryPlan(param.getId());
        if (planModel == null) {
            return null;
        }
        DeliveryPlanResult planResult = new DeliveryPlanResult();
        AssemblerUtils2.assemble(planModel, planResult);
        planResult.setId(String.valueOf(planModel.getId()));
        planResult.setResSlotId(String.valueOf(planModel.getResSlotId()));
        planResult.setDeliveryRuleModel(planModel.getDeliveryRuleModel());
        planResult.setMaterialModel(planModel.getMaterialModel());
        return planResult;
    }

    private DeliveryPlanEntity getAndCheck(Long id) {
        DeliveryPlanEntity entity = deliveryPlanRepository.getById(id);
        AssertUtils.notNull(entity, IResponseStatusMsg.APIEnum.PARAM_ERROR, "该投放计划不存在或已删除");
        return entity;
    }
}
